﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;

using LumiSoft.Net.RTP;
using LumiSoft.Net.Media.Codec.Audio;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;

namespace LumiSoft.Net.Media
{
    /// <summary>
    /// This class implements audio-in (eg. microphone,line-in device) device RTP audio sending.
    /// </summary>
    public class AudioIn_RTP : IDisposable
    {
        //Object representing the microphone of the device
        private Microphone microphone = Microphone.Default;

        //Dynamic buffer to retrieve audio data from the microphone
        private byte[] recordBuffer;

        private bool m_IsDisposed = false;
        private bool m_IsRunning = false;
        //private AudioInDevice m_pAudioInDevice = null;
        private int m_AudioFrameSize = 20;  //ms
        private Dictionary<int, AudioCodec> m_pAudioCodecs = null;

        //TODO: Finish this function
        private RTP_SendStream m_pRTP_Stream = null;

        private AudioCodec m_pActiveCodec = null;
        //private _WaveIn m_pWaveIn = null;
        private uint m_RtpTimeStamp = 0;

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="audioFrameSize">Audio frame size in milliseconds.</param>
        /// <param name="codecs">Audio codecs with RTP payload number. For example: 0-PCMU,8-PCMA.</param>
        /// <param name="stream">RTP stream to use for audio sending.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>audioInDevice</b>,<b>codecs</b> or <b>stream</b> is null reference.</exception>
        public AudioIn_RTP(int audioFrameSize, Dictionary<int, AudioCodec> codecs, RTP_SendStream stream)
        {
            if (codecs == null)
            {
                throw new ArgumentNullException("codecs");
            }
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            m_AudioFrameSize = audioFrameSize;
            m_pAudioCodecs = codecs;
            m_pRTP_Stream = stream;

            m_pRTP_Stream.Session.PayloadChanged += new EventHandler(m_pRTP_Stream_PayloadChanged);
            m_pAudioCodecs.TryGetValue(m_pRTP_Stream.Session.Payload, out m_pActiveCodec);      //TODO: Khong hieu?

            // Event handler for getting audio data when the buffer is full
            microphone.BufferReady += new EventHandler<EventArgs>(microphone_BufferReady);
        }

        #region method Dispose

        /// <summary>
        /// Cleans up any resources being used.
        /// </summary>
        public void Dispose()
        {
            if (m_IsDisposed)
            {
                return;
            }

            Stop();

            m_IsDisposed = true;

            this.Error = null;
            m_pAudioCodecs = null;
            m_pRTP_Stream.Session.PayloadChanged -= new EventHandler(m_pRTP_Stream_PayloadChanged);
            m_pRTP_Stream = null;
            m_pActiveCodec = null;
            microphone = null;
        }

        #endregion


        #region Events handling

        #region method m_pRTP_Stream_PayloadChanged

        /// <summary>
        /// Is called when RTP session sending payload has changed.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private void m_pRTP_Stream_PayloadChanged(object sender, EventArgs e)
        {
            if (m_IsRunning)
            {
                Stop();

                m_pActiveCodec = null;
                m_pAudioCodecs.TryGetValue(m_pRTP_Stream.Session.Payload, out m_pActiveCodec);

                Start();
            }
        }

        #endregion

        #region method microphone_BufferReady

        /// <summary>
        /// The Microphone.BufferReady event handler.
        /// Gets the audio data from the microphone and stores it in a buffer,
        /// then writes that buffer to a stream for later playback.
        /// Any action in this event handler should be quick!
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void microphone_BufferReady(object sender, EventArgs e)
        {
            try
            {
                // We don't have RTP timestamp base or time stamp recycled.
                if (m_RtpTimeStamp == 0 || m_RtpTimeStamp > m_pRTP_Stream.Session.RtpClock.RtpTimestamp)
                {
                    m_RtpTimeStamp = m_pRTP_Stream.Session.RtpClock.RtpTimestamp;
                }
                // Some sample block missing or silence suppression.
                // Don't work ... need some more investigation.
                //else if((m_pRTP_Stream.Session.RtpClock.RtpTimestamp - m_RtpTimeStamp) > 2 * m_pRTP_Stream.Session.RtpClock.MillisecondsToRtpTicks(m_AudioFrameSize)){
                //    m_RtpTimeStamp = m_pRTP_Stream.Session.RtpClock.RtpTimestamp;
                //}
                else
                {
                    m_RtpTimeStamp += (uint)m_pRTP_Stream.Session.RtpClock.MillisecondsToRtpTicks(m_AudioFrameSize);
                }

                if (m_pActiveCodec != null)
                {
                    // Retrieve audio data
                    microphone.GetData(recordBuffer);

                    RTP_Packet rtpPacket = new RTP_Packet();
                    rtpPacket.Data = m_pActiveCodec.Encode(recordBuffer, 0, recordBuffer.Length);
                    rtpPacket.Timestamp = m_RtpTimeStamp;

                    m_pRTP_Stream.Send(rtpPacket);
                }
            }
            catch (Exception x)
            {
                if (!this.IsDisposed)
                {
                    // Raise error event(We can't throw expection directly, we are on threadpool thread).
                    OnError(x);
                }
            }
        }

        #endregion

        #endregion


        #region method Start

        /// <summary>
        /// Starts capturing from the microphone and sending it to RTP stream.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        public void Start()
        {
            if (this.IsDisposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }

            if (m_IsRunning)
            {
                return;
            }

            m_IsRunning = true;

            if (m_pActiveCodec != null)
            {
                //// Calculate buffer size. The number of bytes for a 20 ms timespan
                //int bufferSize = (m_pActiveCodec.AudioFormat.SamplesPerSecond / (1000 / m_AudioFrameSize)) * (m_pActiveCodec.AudioFormat.BitsPerSample / 8);

                ////m_pWaveIn = new _WaveIn(m_pAudioInDevice, m_pActiveCodec.AudioFormat.SamplesPerSecond, m_pActiveCodec.AudioFormat.BitsPerSample, 1, bufferSize);
                ////m_pWaveIn.AudioFrameReceived += new EventHandler<EventArgs<byte[]>>(m_pWaveIn_AudioFrameReceived);
                ////m_pWaveIn.Start();

                //TODO: xac dinh 
                //Every sample is 100 ms long. Windows phone now does not allow 20 ms long, at least 100 ms and at most 1000 ms
                microphone.BufferDuration = TimeSpan.FromMilliseconds(100); //Can not set to 20 ms

                // Allocate memory to hold the audio data
                recordBuffer = new byte[microphone.GetSampleSizeInBytes(microphone.BufferDuration)];

                //Start recording
                microphone.Start();
            }
        }

        #endregion

        #region method Stop

        /// <summary>
        /// Stops capturing from audio-in device and sending it to RTP stream.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        public void Stop()
        {
            if (this.IsDisposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if (!m_IsRunning)
            {
                return;
            }
        }

        #endregion


        #region Properties implementation

        /// <summary>
        /// Gets if this object is disposed.
        /// </summary>
        public bool IsDisposed
        {
            get { return m_IsDisposed; }
        }

        /// <summary>
        /// Gets if currently audio is sent.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        public bool IsRunning
        {
            get
            {
                if (this.IsDisposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                return m_IsRunning;
            }
        }

        /// <summary>
        /// Gets audio-in device is used to capture sound.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when null reference is passed.</exception>
        public AudioInDevice AudioInDevice
        {
            get
            {
                if (this.IsDisposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                return m_pAudioInDevice;
            }

            set
            {
                if (this.IsDisposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                if (value == null)
                {
                    throw new ArgumentNullException("AudioInDevice");
                }

                m_pAudioInDevice = value;

                if (this.IsRunning)
                {
                    Stop();
                    Start();
                }
            }
        }

        // TODO:
        // public int Volume ?

        /// <summary>
        /// Gets RTP stream used for audio sending.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        public RTP_SendStream RTP_Stream
        {
            get
            {
                if (this.IsDisposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                return m_pRTP_Stream;
            }
        }

        /// <summary>
        /// Gets current audio codec what is used for sending.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        public AudioCodec AudioCodec
        {
            get
            {
                if (this.IsDisposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                return m_pActiveCodec;
            }
        }

        /// <summary>
        /// Gets or sets audio codecs.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when null reference passed.</exception>
        public Dictionary<int, AudioCodec> AudioCodecs
        {
            get
            {
                if (this.IsDisposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                return m_pAudioCodecs;
            }

            set
            {
                if (this.IsDisposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                if (value == null)
                {
                    throw new ArgumentNullException("AudioCodecs");
                }

                m_pAudioCodecs = value;
            }
        }

        #endregion

        #region Events implementation

        /// <summary>
        /// This method is raised when asynchronous thread Exception happens.
        /// </summary>
        public event EventHandler<ExceptionEventArgs> Error = null;

        #region method OnError

        /// <summary>
        /// Raises <b>Error</b> event.
        /// </summary>
        /// <param name="x">Error what happened.</param>
        private void OnError(Exception x)
        {
            if (this.Error != null)
            {
                this.Error(this, new ExceptionEventArgs(x));
            }
        }

        #endregion

        #endregion
    }
}
